#ifndef CANON_LOG_
#define CANON_LOG_

#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <list>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <map>
#include "util/singleton.h"
#include "util/utils.h"

#define CANON_LOG_LEVEL(logger, level)                                                                         \
    if (logger->getLevel() <= level)                                                                           \
    canon::LogEventWrap(                                                                                       \
        canon::LogEvent::ptr(new canon::LogEvent(logger, level, __FILE__, __FUNCTION__, __LINE__, 0,           \
                                                 canon::getThreadId(), canon::getFiberId(), time((nullptr))))) \
        .getSS()

#define CANON_LOG_DEBUG(logger) CANON_LOG_LEVEL(logger, canon::LogLevel::DEBUG)
#define CANON_LOG_INFO(logger) CANON_LOG_LEVEL(logger, canon::LogLevel::INFO)
#define CANON_LOG_WARN(logger) CANON_LOG_LEVEL(logger, canon::LogLevel::WARN)
#define CANON_LOG_ERROR(logger) CANON_LOG_LEVEL(logger, canon::LogLevel::ERROR)
#define CANON_LOG_FATAL(logger) CANON_LOG_LEVEL(logger, canon::LogLevel::FATAL)

#define CANON_LOG_ROOT() canon::LoggerMgr::GetInstance()->getRoot()
#define CANON_LOG_NAME(name) canon::LoggerMgr::GetInstance()->getLogger(name)

namespace canon {

class Logger;
class LoggerManager;

class LogLevel
{
public:
    enum Level
    {
        UNKNOWN = 0,
        DEBUG = 1,
        INFO = 2,
        WARN = 3,
        ERROR = 4,
        FATAL = 5
    };

    static const char *ToString(LogLevel::Level level);
    static LogLevel::Level FromString(const std::string &str);
};

// 日志事件
class LogEvent
{
public:
    using ptr = std::shared_ptr<LogEvent>;

    explicit LogEvent(std::shared_ptr<Logger> logger, LogLevel::Level level, const char *file, const char *funcName,
                      int32_t line, uint32_t elapse, uint32_t threadId, uint32_t fiberId, uint64_t time)
        : m_logger(std::move(logger)),
          m_level(level),
          m_file(file),
          m_functionName(funcName),
          m_line(line),
          m_elapse(elapse),
          m_threadId(threadId),
          m_fiberId(fiberId),
          m_time(time)
    {
    }

    const char *getFile() const { return m_file; }
    const char *getFunctionName() const { return m_functionName; }
    uint32_t getLine() const { return m_line; }
    uint32_t getThreadId() const { return m_threadId; }
    const std::string &getThreadName() const { return m_threadName; }
    uint32_t getElapse() const { return m_elapse; }
    uint32_t getFiberId() const { return m_fiberId; }
    uint64_t getTime() const { return m_time; }
    std::string getContent() const { return m_ss.str(); }
    std::shared_ptr<Logger> getLogger() const { return m_logger; }
    LogLevel::Level getLevel() const { return m_level; }
    std::stringstream &getSS() { return m_ss; }
    void format(const char *fmt, ...);
    void format(const char *fmt, va_list al);

private:
    const char *m_file = nullptr;
    const char *m_functionName = nullptr;
    int32_t m_line = 0;
    uint32_t m_elapse = 0;
    uint32_t m_threadId = 0;
    uint32_t m_fiberId = 0;
    uint64_t m_time = 0;
    std::string m_threadName;
    std::stringstream m_ss;
    std::shared_ptr<Logger> m_logger;
    LogLevel::Level m_level;
};

class LogEventWrap
{
public:
    explicit LogEventWrap(LogEvent::ptr e);
    ~LogEventWrap();
    std::stringstream &getSS();

private:
    LogEvent::ptr m_event{nullptr};
};

// 日志格式器
class LogFormatter
{
public:
    using ptr = std::shared_ptr<LogFormatter>;
    explicit LogFormatter(std::string pattern) : m_pattern(pattern) { init(); }
    void init();
    std::string format(const std::shared_ptr<Logger> &logger, LogLevel::Level level, const LogEvent::ptr &event);
    std::ostream &format(std::ostream &ofs, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event);
    bool isError() const { return m_error; }
    const std::string getPattern() const { return m_pattern; }

public:
    class FormatItem
    {
    public:
        using ptr = std::shared_ptr<FormatItem>;
        virtual ~FormatItem() = default;
        virtual void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level,
                            LogEvent::ptr event) = 0;
    };

private:
    std::string m_pattern;
    std::list<FormatItem::ptr> m_items;
    /// 是否有错误
    bool m_error = false;
};

// 日志输出地
class LogAppender
{
    friend class Logger;

public:
    using Ptr = std::shared_ptr<LogAppender>;
    virtual ~LogAppender() = default;
    virtual void log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) = 0;
    void setFormatter(LogFormatter::ptr format)
    {
        m_formatter = std::move(format);
        if (m_formatter) {
            m_hasFormatter = true;
        } else {
            m_hasFormatter = false;
        }
    }

    LogFormatter::ptr gerFormat() { return m_formatter; }
    void setLevel(LogLevel::Level level) { m_level = level; }
    LogLevel::Level getLevel() const { return m_level; }
    virtual std::string toYamlString() = 0;

protected:
    LogLevel::Level m_level;
    LogFormatter::ptr m_formatter{nullptr};
    /// 是否有自己的日志格式器
    bool m_hasFormatter = false;
};

// 日志器
class Logger : public std::enable_shared_from_this<Logger>
{
    friend class LoggerManager;

public:
    using ptr = std::shared_ptr<Logger>;
    explicit Logger(std::string name = "root");
    void log(LogLevel::Level level, const LogEvent::ptr &event);
    void debug(const LogEvent::ptr &event);
    void info(const LogEvent::ptr &event);
    void warn(const LogEvent::ptr &event);
    void error(const LogEvent::ptr &event);
    void fatal(const LogEvent::ptr &event);

    void addAppender(const LogAppender::Ptr &appender);
    void deleteAppender(const LogAppender::Ptr &appender);
    void clearAppenders() { m_appenders.clear(); }

    void setLevel(LogLevel::Level level) { m_level = level; };
    auto getLevel() const { return m_level; }

    const std::string &getName() const { return m_name; }
    void setName(const std::string &name) { m_name = name; }

    void setFormatter(const LogFormatter::ptr &formatter)
    {
        m_formatter = formatter;
        if (m_root) {
            m_root->setFormatter(formatter);
        }
        for (auto &i : m_appenders) {
            // MutexType::Lock ll(i->m_mutex);
            // if (!i->m_hasFormatter) {
            i->setFormatter(formatter);
            // }
        }
    }

    void setFormatter(const std::string &val)
    {
        canon::LogFormatter::ptr new_val(new LogFormatter(val));
        if (new_val->isError()) {
            std::cout << "Logger setFormatter name=" << m_name << " value=" << val << " invalid formatter" << std::endl;
            return;
        }
        setFormatter(new_val);
    }

    std::string toYamlString();

private:
    std::string m_name;
    LogLevel::Level m_level = LogLevel::DEBUG;
    std::list<LogAppender::Ptr> m_appenders;
    LogFormatter::ptr m_formatter;
    /// 主日志器
    Logger::ptr m_root{nullptr}; // 在m_appenders为空时，使用主日志器
};

class StdoutLogAppender : public LogAppender
{
public:
    using ptr = std::shared_ptr<StdoutLogAppender>;

    void log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override;
    std::string toYamlString() override;
};

class FileLogAppender : public LogAppender
{
public:
    using ptr = std::shared_ptr<FileLogAppender>;
    explicit FileLogAppender(std::string filename);
    void log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override;
    bool reopen();
    std::string toYamlString() override;

private:
    std::string m_filename;
    std::ofstream m_filestream;
};

class LoggerManager
{
public:
    using ptr = std::shared_ptr<LoggerManager>;
    LoggerManager();
    ~LoggerManager() = default;
    void init();
    Logger::ptr getLogger(const std::string &name);
    Logger::ptr getRoot() const { return m_root; }
    std::string toYamlString();

private:
    std::map<std::string, Logger::ptr> m_loggers;
    // 主日志器
    Logger::ptr m_root{nullptr};
};

using LoggerMgr = canon::Singleton<LoggerManager>;

} // namespace canon

#endif